/*
Copyright (c) 2013 Mihail Volkov

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include "consoleapp.h"
#include <QAction>
#include <QMessageBox>
#include <QLineEdit>
#include <QStatusBar>
#include <QNetworkProxy>
#include <QWebSecurityOrigin>
#include <windows.h>

HtMdConsoleApp *HtMdConsoleApp::spInstance = NULL;

HtMdConsoleApp::HtMdConsoleApp (int &argc,char **argv):
	QApplication (argc,argv),
	mpFileDialog (NULL),
	mSettings ("MikleSoft","Hatul Madan"),
	locationsChanged (false),
	fileMode (FM_NONE)
{
	spInstance = this;

	mpConnection = new HtMdConnection (this);

	// setup machine:
	evtAppStarted.name = "evtAppStarted";
	evtConnectDialogOpen.name = "evtConnectDialogOpen";
	evtConnectDialogOk.name = "evtConnectDialogOk";
	evtConnectDialogCancel.name = "evtConnectDialogCancel";
	evtConnectionEstablished.name = "evtConnectionEstablished";
	evtConnectionFailed.name = "evtConnectionFailed";
	evtConnectionServerDropped.name = "evtConnectionServerDropped";
	evtConnectionClientDropped.name = "evtConnectionClientDropped";
	evtCmdDisconnect.name = "evtCmdDisconnect";
	evtCmdExit.name = "evtCmdExit";

	// begin state
	stateBegin.name = "Begin";
	QObject::connect(&stateBegin,SIGNAL(changedTo(SMMachine *)),
		this,SLOT(onEnterStateBegin(SMMachine *)));
	stateBegin.addTransition (stateDisconnected,
		evtAppStarted);

	// shutdown state
	stateShutdown.name = "Shutdown";
	QObject::connect(&stateShutdown,SIGNAL(changedTo(SMMachine *)),
		this,SLOT(onEnterStateShutdown(SMMachine *)));
	stateShutdown.addTransition (stateEnd,
		evtConnectionClientDropped|evtConnectionServerDropped);

	// end state
	stateEnd.name = "End";
	QObject::connect(&stateEnd,SIGNAL(changedTo(SMMachine *)),
		this,SLOT(onEnterStateEnd(SMMachine *)));

	// disconnected state
	stateDisconnected.name = "Disconnected";
	QObject::connect(&stateDisconnected,SIGNAL(changedTo(SMMachine *)),
		this,SLOT(onEnterStateDisconnected(SMMachine *)));
	stateDisconnected.addTransition (stateConnectDialog,
		evtConnectDialogOpen);
	stateDisconnected.addTransition (stateEnd,
		evtCmdExit);

	// connect dialog state
	stateConnectDialog.name = "ConnectDialog";
	QObject::connect(&stateConnectDialog,SIGNAL(changedTo(SMMachine *)),
		this,SLOT(onEnterStateConnectDialog(SMMachine *)));
	stateConnectDialog.addTransition (stateConnecting,
		evtConnectDialogOk);
	stateConnectDialog.addTransition (stateDisconnected,
		evtConnectDialogCancel);
	stateConnectDialog.addTransition (stateEnd,
		evtCmdExit);

	// connecting state
	stateConnecting.name = "Connecting";
	QObject::connect(&stateConnecting,SIGNAL(changedTo(SMMachine *)),
		this,SLOT(onEnterStateConnecting(SMMachine *)));
	stateConnecting.addTransition (stateConnected,
		evtConnectionEstablished);
	stateConnecting.addTransition (stateConnectFailed,
		evtConnectionFailed);
	stateConnecting.addTransition (stateDisconnecting,
		evtCmdDisconnect);
	stateConnecting.addTransition (stateShutdown,
		evtCmdExit);

	// connect failed state
	stateConnectFailed.name = "ConnectFailed";
	QObject::connect(&stateConnectFailed,SIGNAL(changedTo(SMMachine *)),
		this,SLOT(onEnterStateConnectFailed(SMMachine *)));
	stateConnectFailed.addTransition (stateConnectDialog);
	// ^TODO: show "connection failed" dialog
	stateConnectFailed.addTransition (stateShutdown,
		evtCmdExit);

	// disconnecting state
	stateDisconnecting.name = "Disconnecting";
	QObject::connect(&stateDisconnecting,SIGNAL(changedTo(SMMachine *)),
		this,SLOT(onEnterStateDisconnecting(SMMachine *)));
	stateDisconnecting.addTransition (stateDisconnected,
		evtConnectionServerDropped|evtConnectionClientDropped);

	// connected state
	stateConnected.name = "Connected";
	QObject::connect(&stateConnected,SIGNAL(changedTo(SMMachine *)),
		this,SLOT(onEnterStateConnected(SMMachine *)));
	stateConnected.addTransition (stateDisconnecting,
		evtCmdDisconnect);
	stateConnected.addTransition(stateDisconnected,
		evtConnectionServerDropped|evtConnectionClientDropped);

	// setup file uploading/downloading machine:
	evtFdmDownloadRequest.name = "evtFdmDownloadRequest";
	evtFdmUploadRequest.name = "evtFdmUploadRequest";
	evtFdmCancel.name = "evtFdmCancel";
	evtFdmCompleted.name = "evtFdmCompleted";

	// no dialog or process state
	stateFdmNone.name = "FdmNone";
	QObject::connect(&stateFdmNone,SIGNAL(changedTo(SMMachine*)),
		this,SLOT(onEnterStateFdmNone(SMMachine*)));
	stateFdmNone.addTransition (stateFdmDownload,
		evtFdmDownloadRequest);
	stateFdmNone.addTransition (stateFdmUpload,
		evtFdmUploadRequest);

	// download name request/confirmation dialog shown
	stateFdmDownload.name = "FdmDownload";
	QObject::connect(&stateFdmDownload,SIGNAL(changedTo(SMMachine*)),
		this,SLOT(onEnterStateFdmDownload(SMMachine*)));
	stateFdmDownload.addTransition (stateFdmCancelled,
		evtFdmCancel);
	stateFdmDownload.addTransition (stateFdmDownloadInProgress,
		evtFdmConfirm);

	// download in progress dialog shown
	stateFdmDownloadInProgress.name = "FdmDownloadInProgress";
	QObject::connect(&stateFdmDownloadInProgress,SIGNAL(changedTo(SMMachine*)),
		this,SLOT(onEnterStateFdmDownloadInProgress(SMMachine*)));
	stateFdmDownloadInProgress.addTransition (stateFdmCancelled,
		evtFdmCancel);
	stateFdmDownloadInProgress.addTransition (stateFdmNone,
		evtFdmCompleted);

	// upload name request/confirmation dialog shown
	stateFdmUpload.name = "FdmUpload";
	QObject::connect(&stateFdmUpload,SIGNAL(changedTo(SMMachine*)),
		this,SLOT(onEnterStateFdmUpload(SMMachine*)));
	stateFdmUpload.addTransition (stateFdmCancelled,
		evtFdmCancel);
	stateFdmUpload.addTransition (stateFdmUploadInProgress,
		evtFdmConfirm);

	// upload in progress dialog shown
	stateFdmUploadInProgress.name = "FdmUploadInProgress";
	QObject::connect(&stateFdmUploadInProgress,SIGNAL(changedTo(SMMachine*)),
		this,SLOT(onEnterStateFdmUploadInProgress(SMMachine*)));
	stateFdmUploadInProgress.addTransition (stateFdmCancelled,
		evtFdmCancel);
	stateFdmUploadInProgress.addTransition (stateFdmNone,
		evtFdmCompleted);

	// dialog or process cancelled/aborted
	stateFdmCancelled.name = "FdmCancelled";
	QObject::connect(&stateFdmCancelled,SIGNAL(changedTo(SMMachine*)),
		this,SLOT(onEnterStateFdmCancelled(SMMachine*)));
	stateFdmCancelled.addTransition (stateFdmNone);

	// setup widgets, actions and other stuff:
	mpMainWindow = new MainWindow ();
	mpConnectDialog = new ConnectDialog (mpMainWindow);
	mpAboutDialog = new AboutDialog (mpMainWindow);
	mpFileProgressDialog = new QProgressDialog (mpMainWindow,Qt::Dialog);
	mpFileProgressDialog->setWindowModality(Qt::ApplicationModal);

	QObject::connect (getGlobalAction ("actionConnect"),
		SIGNAL(triggered()),&evtConnectDialogOpen,SLOT(trigger()));
	QObject::connect (getGlobalAction ("actionDisconnect"),
		SIGNAL(triggered()),&evtCmdDisconnect,SLOT(trigger()));
	QObject::connect (getGlobalAction ("actionExit"),
		SIGNAL(triggered()),&evtCmdExit,SLOT(trigger()));
	QObject::connect (getGlobalAction ("actionDocumentation_ru"),
		SIGNAL(triggered()),this,SLOT(showDocRu()));
	QObject::connect (getGlobalAction ("actionDocumentation_en"),
		SIGNAL(triggered()),this,SLOT(showDocEn()));
	QObject::connect (getGlobalAction ("actionAbout"),
		SIGNAL(triggered()),this,SLOT(showAbout()));

	QObject::connect (mpConnectDialog->findChild<QAction *> ("actionConnect"),
		SIGNAL(triggered()),&evtConnectDialogOk,SLOT(trigger()));
	QObject::connect (mpConnectDialog->findChild<QAction *> ("actionCancel"),
		SIGNAL(triggered()),&evtConnectDialogCancel,SLOT(trigger()));

	QObject::connect (mpFileProgressDialog,SIGNAL(canceled()),
		&evtFdmCancel,SLOT(trigger()));

	mpMainWindow->setWindowIcon(QIcon(":/icons/hatul.png"));
	mpMainWindow->showMaximized ();

	// mark our URL schemes as local
	QWebSecurityOrigin::addLocalScheme("htmd");
	QWebSecurityOrigin::addLocalScheme("ass");

	// launch the system
	appMachine.setCurrentState (stateBegin);
	fileDialogMachine.setCurrentState (stateFdmNone);
}

HtMdConsoleApp::~HtMdConsoleApp ()
{
	while (!mUnits.empty ())
	{
		delete mUnits.begin().value();
		mUnits.erase (mUnits.begin());
	}
	delete mpMainWindow;
}

void HtMdConsoleApp::saveSettings ()
{
	// fetch parameters
	connectParameters = mpConnectDialog
		->findChild<QLineEdit *> ("connectParameters")
		->property ("text").toString ()
		.trimmed ();
	kitLocation = mpConnectDialog
		->findChild<QLineEdit *> ("kitLocation")
		->property ("text").toString ()
		.trimmed ();
	assetsLocation = mpConnectDialog
		->findChild<QLineEdit *> ("assetsLocation")
		->property ("text").toString ()
		.trimmed ();

	mSettings.setValue ("connectParameters",connectParameters);
	mSettings.setValue ("kitLocation",kitLocation);
	mSettings.setValue ("assetsLocation",assetsLocation);
}

void HtMdConsoleApp::restoreSettings ()
{
	connectParameters = mSettings.value ("connectParameters","127.0.0.1:1234")
		.toString();
	kitLocation = mSettings.value ("kitLocation","")
		.toString();
	assetsLocation = mSettings.value ("assetsLocation","")
		.toString();

	mpConnectDialog->findChild<QLineEdit *> ("connectParameters")
		->setProperty ("text",connectParameters);
	mpConnectDialog->findChild<QLineEdit *> ("kitLocation")
		->setProperty ("text",kitLocation);
	mpConnectDialog->findChild<QLineEdit *> ("assetsLocation")
		->setProperty ("text",assetsLocation);
}

void HtMdConsoleApp::createFileDialog ()
{
	disposeFileDialog ();

	mpFileDialog = new QFileDialog (mpMainWindow,Qt::Dialog);
	mpFileDialog->setWindowModality (Qt::ApplicationModal);
	mpFileDialog->setDirectory (lastFileDir);

	QObject::connect (mpFileDialog,SIGNAL(accepted()),
		&evtFdmConfirm,SLOT(trigger()));
	QObject::connect (mpFileDialog,SIGNAL(rejected()),
		&evtFdmCancel,SLOT(trigger()));
}

void HtMdConsoleApp::disposeFileDialog ()
{
	if (mpFileDialog!=NULL)
	{
		lastFileDir = mpFileDialog->directory();
		mpFileDialog->setParent(NULL);
		delete mpFileDialog;
		mpFileDialog = NULL;
	}
}

void HtMdConsoleApp::closeAllUnits ()
{
	while (!mUnits.empty())
		closeUnit (mUnits.begin().key());
}

// state arrival handlers
void HtMdConsoleApp::onEnterStateBegin (SMMachine *)
{
	// begin state: program startup
	mpMainWindow->findChild<QAction *> ("actionConnect")
		->setProperty ("enabled",false);
	mpMainWindow->findChild<QAction *> ("actionDisconnect")
		->setProperty ("enabled",false);

	restoreSettings ();
	lastFileDir = kitLocation;

	// state entered
	evtAppStarted.trigger ();
}

void HtMdConsoleApp::onEnterStateShutdown (SMMachine *)
{
	mpMainWindow->findChild<QAction *> ("actionConnect")
		->setProperty ("enabled",false);
	mpMainWindow->findChild<QAction *> ("actionDisconnect")
		->setProperty ("enabled",false);

	// initiate disconnect
	QObject::connect(mpConnection,SIGNAL(disconnected()),
		&evtConnectionClientDropped,SLOT(trigger()));
	HtMdConnection::disconnect (); // initiate disconnection
	evtFdmCancel.trigger (); // file download/upload cancel
	// TODO: statusbar = "exitting"
}

void HtMdConsoleApp::onEnterStateEnd (SMMachine *)
{
	// save session connect params
	saveSettings ();

	// exit
	this->exit (0);
}

void HtMdConsoleApp::onEnterStateDisconnected (SMMachine *)
{
	mpMainWindow->findChild<QAction *> ("actionConnect")
		->setProperty ("enabled",true);
	mpMainWindow->findChild<QAction *> ("actionDisconnect")
		->setProperty ("enabled",false);

	// TODO: statusbar = "Ready"
	mpConnectDialog->hide ();

	// disconnect irrelevant actions
	QObject::disconnect(mpConnection,SIGNAL(connected()),
		&evtConnectionEstablished,SLOT(trigger()));
	QObject::disconnect(mpConnection,SIGNAL(disconnected()),
		&evtConnectionFailed,SLOT(trigger()));
	QObject::disconnect(mpConnection,SIGNAL(disconnected()),
		&evtConnectionServerDropped,SLOT(trigger()));
	QObject::disconnect(mpConnection,SIGNAL(disconnected()),
		&evtConnectionClientDropped,SLOT(trigger()));

	// notify units
	for (Units::iterator uit=mUnits.begin(); uit!=mUnits.end(); ++uit)
		uit.value()->onDisconnected ();
	evtFdmCancel.trigger (); // file download/upload cancel
}

void HtMdConsoleApp::onEnterStateConnectDialog (SMMachine *)
{
	mpMainWindow->findChild<QAction *> ("actionConnect")
		->setProperty ("enabled",false);
	mpMainWindow->findChild<QAction *> ("actionDisconnect")
		->setProperty ("enabled",false);

	// notify units (we can enter here because of disconnect,
	// don't bother if onDisconnected for some unit is called
	// twice, unit has a safety harness for this case)
	for (Units::iterator uit=mUnits.begin(); uit!=mUnits.end(); ++uit)
		uit.value()->onDisconnected ();

	// restore session connect params
	restoreSettings ();

	// TODO: clear statusbar
	mpConnectDialog->show ();
}

void HtMdConsoleApp::onEnterStateConnecting (SMMachine *)
{
	if (locationsChanged)
	{
		locationsChanged = false;
		closeAllUnits ();
	}

	mpMainWindow->findChild<QAction *> ("actionConnect")
		->setProperty ("enabled",false);
	mpMainWindow->findChild<QAction *> ("actionDisconnect")
		->setProperty ("enabled",true);

	// TODO: statusbar = "connecting to..."
	mpConnectDialog->hide ();

	// and save the settings from the dialog
	saveSettings ();

	// initiate connection
	QObject::connect(mpConnection,SIGNAL(connected()),
		&evtConnectionEstablished,SLOT(trigger()));
	QObject::connect(mpConnection,SIGNAL(disconnected()),
		&evtConnectionFailed,SLOT(trigger()));
	HtMdConnection::connect ();
}

void HtMdConsoleApp::onEnterStateConnectFailed (SMMachine *)
{
	// disarm actions
	QObject::disconnect(mpConnection,SIGNAL(connected()),
		&evtConnectionEstablished,SLOT(trigger()));
	QObject::disconnect(mpConnection,SIGNAL(disconnected()),
		&evtConnectionFailed,SLOT(trigger()));

	mpMainWindow->findChild<QAction *> ("actionConnect")
		->setProperty ("enabled",false);
	mpMainWindow->findChild<QAction *> ("actionDisconnect")
		->setProperty ("enabled",false);

	// show failure notification dialog
	if (HtMdConnection::getDisconnectionReason ()!="")
	{
		QMessageBox::warning (mpMainWindow,tr("Connection failed"),
			HtMdConnection::getDisconnectionReason());
	}
	mpMainWindow->findChild<QAction *> ("actionDisconnect")
		->setProperty ("enabled",true);
}

void HtMdConsoleApp::onEnterStateDisconnecting (SMMachine *)
{
	mpMainWindow->findChild<QAction *> ("actionConnect")
		->setProperty ("enabled",false);
	mpMainWindow->findChild<QAction *> ("actionDisconnect")
		->setProperty ("enabled",false);

	QObject::connect(mpConnection,SIGNAL(disconnected()),
		&evtConnectionClientDropped,SLOT(trigger()));

	HtMdConnection::disconnect (); // initiate disconnection
	// TODO: statusbar = "disconnecting"
}

void HtMdConsoleApp::onEnterStateConnected (SMMachine *)
{
	mpMainWindow->findChild<QAction *> ("actionConnect")
		->setProperty ("enabled",false);
	mpMainWindow->findChild<QAction *> ("actionDisconnect")
		->setProperty ("enabled",true);

	// TODO: statusbar = "connected to..."

	// re-connect signals
	QObject::disconnect(mpConnection,SIGNAL(connected()),
		&evtConnectionEstablished,SLOT(trigger()));
	QObject::disconnect(mpConnection,SIGNAL(disconnected()),
		&evtConnectionFailed,SLOT(trigger()));
	QObject::connect(mpConnection,SIGNAL(disconnected()),
		&evtConnectionServerDropped,SLOT(trigger()));
}

// file process state arrival handlers
void HtMdConsoleApp::onEnterStateFdmNone (SMMachine *)
{
	disposeFileDialog ();
	mpFileProgressDialog->hide ();
}

void HtMdConsoleApp::onEnterStateFdmDownload (SMMachine *)
{
	fileMode = FM_DOWNLOAD;
	createFileDialog ();
	mpFileDialog->setAcceptMode (QFileDialog::AcceptSave);
	mpFileDialog->setFileMode (QFileDialog::AnyFile);
	mpFileDialog->setNameFilter (fileSuggestedFilter);
	mpFileDialog->setWindowTitle (tr("Save downloaded file as..."));
	mpFileDialog->selectFile (APP.fileSuggestedName);
	mpFileDialog->show ();
}

void HtMdConsoleApp::onEnterStateFdmDownloadInProgress (SMMachine *)
{
	fileSelectedName = mpFileDialog->selectedFiles()[0];
	mpFileDialog->hide ();
	QFileInfo fi (fileSelectedName);
	mpFileProgressDialog->setWindowTitle (
		tr ("Downloading file %1").arg(fi.fileName()));
	mpFileProgressDialog->setRange(0,100);
	mpFileProgressDialog->setValue(0);
	mpFileProgressDialog->show ();
	emit fileConfirmed (fileSelectedName,false);
}

void HtMdConsoleApp::onEnterStateFdmUpload (SMMachine *)
{
	fileMode = FM_UPLOAD;
	createFileDialog ();
	mpFileDialog->setAcceptMode (QFileDialog::AcceptOpen);
	mpFileDialog->setFileMode (QFileDialog::ExistingFile);
	mpFileDialog->setNameFilter (fileSuggestedFilter);
	mpFileDialog->setWindowTitle (tr("Select file to upload"));
	mpFileDialog->show ();
}

void HtMdConsoleApp::onEnterStateFdmUploadInProgress (SMMachine *)
{
	fileSelectedName = mpFileDialog->selectedFiles()[0];
	mpFileDialog->hide ();
	QFileInfo fi (fileSelectedName);
	mpFileProgressDialog->setWindowTitle (
		tr ("Uploading file %1").arg(fi.fileName()));
	mpFileProgressDialog->setRange(0,100);
	mpFileProgressDialog->setValue(0);
	mpFileProgressDialog->show ();
	emit fileConfirmed (fileSelectedName,true);
}

void HtMdConsoleApp::onEnterStateFdmCancelled (SMMachine *)
{
	mpFileDialog->hide ();
	mpFileProgressDialog->hide ();
	emit fileCancelled ();
}

// business logic
Unit *HtMdConsoleApp::getUnit (unsigned unitId)
{
	Units::iterator it = mUnits.find (unitId);
	if (it!=mUnits.end ()) return it.value();
	else return NULL;
}

Unit *HtMdConsoleApp::getOrCreateUnit (unsigned unitId)
{
	Unit *pUnit = getUnit (unitId);
	if (pUnit==NULL)
		pUnit = mUnits[unitId] = new Unit (unitId);
	return pUnit;
}

void HtMdConsoleApp::closeUnit (unsigned unitId)
{
	Unit *pUnit = getUnit (unitId);
	mUnits.remove (unitId);
	if (pUnit!=NULL) delete pUnit;
}

void HtMdConsoleApp::setFileProgress (int bytesSofar,int bytesMax)
{
	mpFileProgressDialog->setRange (0,bytesMax);
	mpFileProgressDialog->setValue (bytesSofar);
}

void HtMdConsoleApp::setStatusText (const QString &text)
{
	mpMainWindow->findChild<QStatusBar *> ("statusBar")
		->showMessage (text);
}

bool HtMdConsoleApp::isConnected ()
{
	return mpConnection->mState==HtMdConnection::S_CONNECTED;
}

QTextBrowser *HtMdConsoleApp::getJSErrorConsole ()
{
	static QTextBrowser *result =
		mpMainWindow->findChild<QTextBrowser *> ("errorsBrowser");
	return result;
}

QAction *HtMdConsoleApp::getGlobalAction (const QString &actionName)
{
	return mpMainWindow->findChild<QAction *> (actionName);
}

void HtMdConsoleApp::showAbout ()
{
	mpAboutDialog->show ();
}

void HtMdConsoleApp::showDocRu ()
{
	ShellExecuteW (NULL,L"open",
		(QCoreApplication::applicationDirPath()+
		"/documentation_ru.html").toStdWString().c_str(),
		NULL,NULL,SW_SHOW);
}

void HtMdConsoleApp::showDocEn ()
{
	ShellExecuteW (NULL,L"open",
		(QCoreApplication::applicationDirPath()+
		"/documentation_en.html").toStdWString().c_str(),
		NULL,NULL,SW_SHOW);
}

//
// htmd: and ass: protocols support
//

QString HtMdConsoleApp::decodeSchemePath (const QString &path)
{
	QUrl url = path;
	//qDebug ()<<"Decoding:"<<path;

	QString baseDir;
	if (url.scheme()=="htmd")
		baseDir = APP.kitLocation;
	else if (url.scheme()=="ass")
		baseDir = APP.assetsLocation;
	else return QString::null;
	// '/' betweeb location dir and file path is assumed to be
	// a part of path (htmd:/path/file or ass:/path/file)
	if (baseDir=="") return QString::null; // location not specified
	baseDir = QDir (baseDir).canonicalPath();
	//qDebug()<<"Base dir:"<<baseDir;
	//qDebug()<<"The path:"<<baseDir+url.authority()+url.path();

	QFileInfo fi (baseDir+url.authority()+url.path());
	QString fullPath = fi.dir().canonicalPath()+"/"+fi.fileName();
	// ^so sophisticated bcuz fucken qt returns empty string for
	// canonicalPath of non-existing file and it isn't something
	// we sometimes need
	if (fullPath.left(baseDir.length())
		!=baseDir)
	{
		//qDebug ()<<"path"<<fullPath<<"vs"<<baseDir<<"- not match";
		return QString::null; // directory outside basedir
	}
	//else
		//qDebug ()<<"path"<<fullPath<<"vs"<<baseDir<<"- match";
	return fullPath;
}

HtMdNetworkAccessManager::HtMdNetworkAccessManager(QNetworkAccessManager *manager, QObject *parent)
	: QNetworkAccessManager(parent)
{
	setCache(manager->cache());
	setCookieJar(manager->cookieJar());
	setProxy(manager->proxy());
	setProxyFactory(manager->proxyFactory());
}

QNetworkReply *HtMdNetworkAccessManager::createRequest(
	QNetworkAccessManager::Operation operation, const QNetworkRequest &request,
	QIODevice *device)
{
	if (request.url().scheme() != "htmd" &&
		request.url().scheme() != "ass")
		return QNetworkAccessManager::createRequest(operation, request, device);

	if (operation == GetOperation)
	{
		// Handle our URLs separately by creating custom QNetworkReply
		// objects.
		return new HtMdLocalFileReply (
			APP.decodeSchemePath (request.url().toString()));
	}
	else
		return QNetworkAccessManager::createRequest(operation, request, device);
}

// reply

HtMdLocalFileReply::HtMdLocalFileReply(const QString &fileName):
	mFile (fileName)
{
	mOpenOk = mFile.open (QIODevice::ReadOnly) &&
		open (ReadOnly | Unbuffered);

	mAsyncReadyNotifier.setSingleShot(true);
	//mAsyncReadyNotifier.setInterval(1000);
	mAsyncReadyNotifier.start();
	QObject::connect(&mAsyncReadyNotifier,SIGNAL(timeout()),
		this,SLOT(doNotifyReady()));
}

void HtMdLocalFileReply::abort()
{
	mFile.close ();
}

qint64 HtMdLocalFileReply::bytesAvailable() const
{
	return mFile.bytesAvailable ()+QNetworkReply::bytesAvailable();
	// ^the key gotcha is to not forget QNetworkReply::bytesAvailable()
	// because it already has buffered something for us
}

bool HtMdLocalFileReply::isSequential() const
{
	return true;
}

qint64 HtMdLocalFileReply::readData (char *data, qint64 maxSize)
{
	qint64 bytes = mFile.read (data,maxSize);
	return bytes;
}

void HtMdLocalFileReply::doNotifyReady ()
{
	if (mOpenOk)
	{
		// file open ok
		setHeader(QNetworkRequest::ContentLengthHeader,(int)mFile.size());
		emit readyRead();
		emit finished ();
	}
	else
	{
		// failure!
		setError(QNetworkReply::ContentNotFoundError,
			mFile.errorString());
		emit error(QNetworkReply::ContentNotFoundError);
	}
}
